home *** CD-ROM | disk | FTP | other *** search
/ Visual Basic Toolbox / Visual Basic Toolbox (P.I.E.)(1996).ISO / code_lib / objlibr / objlib12 / vboop.txt < prev    next >
Text File  |  1994-10-15  |  12KB  |  179 lines

  1. Simulating Object Oriented Programming in Visual Basic
  2.  
  3. Compuserve Text posted by Pete Washburn (73750,3141) January 1994
  4.  
  5. Here's some of the techniques I've used to emulate an OOP language with QB and VB.  I must add a couple of comments however.  I'm not saying that Basic is an OOP, but with a few techniques, you can get most of the principles of OOP into Basic.  These are just a few of techniques I've used. They may not be the most efficient or optimum methods available, but they seem to be working for me. Improvements are always accepted.  Debates about  the finer parts of OOP theory are not!  I do a fair amount of work with system simulation, so I'll use a segment of one of my projects to demonstrate.
  6. Most OOP's contain a class tree to define the behavior of objects within the program.  In this demo case, we are modeling a plumbing system, with pipes, hoses, and valves.  The class tree may look as follows:
  7. WaterPart
  8.     Pipe
  9.         Hoses
  10.     Valves
  11.     Pumps
  12.  
  13. We'll look at the WaterPart class.  It is defined as a Function as follows:
  14. Function WaterPart (hObj, msg, value)
  15.  
  16. ' all data for objects of this type is contained within the Function, 
  17. ' within the following instance array
  18.     Static WaterPartInstances() As waterPartObject
  19.  
  20. ' count of individual objects of this type
  21.     Static WaterPartsCount%
  22.  
  23. ' using the hObj passed, find the object's data in the Instance array 
  24. ' (there are a lot more sophisticated and efficient ways to do this, 
  25. ' used as an example only)
  26.     For idx = 1 To WaterPartsCount%
  27.         If WaterPartsInstances(idx).hObj = hObj Then
  28.             Exit For
  29.         End If
  30.     Next idx
  31.  
  32. ' methods
  33.     Select Case msg
  34.         Case GET_QUANTITY
  35.             WaterPart = WaterPartInstances(idx).quantity
  36.         Case SET_QUANTITY
  37.             If value <> WaterPartInstances(idx).quantity Then
  38.                 If WaterPartInstances(idx).diameter = 0 Then
  39.                     value = 0
  40.                 End If
  41.                 WaterPartInstances(idx).quantity = value
  42.             End If
  43.             WaterPart = WaterPartInstances(idx).quantity
  44.         Case GET_DIAMETER
  45.             WaterPart = WaterPartInstances(idx).diameter
  46.         Case SET_DIAMETER
  47.             If value <> WaterPartInstances(idx).diameter Then
  48.                 WaterPartInstances(idx).diameter = value
  49.                 WaterPartInstances(idx).area = .7854 * value ^ 2
  50.             End If
  51.             WaterPart = WaterPartInstances(idx).diameter
  52.         Case GET_AREA
  53.             WaterPart = WaterPartInstances(idx).area
  54.         Case NEW_OBJECT
  55.             WaterPartsCount% = WaterPartsCount% + 1
  56.             idx = WaterPartsCount%
  57.             WaterPartInstances(idx).hObj = hObj
  58.             WaterPart = Self(hObj, INIT_OBJECT, value)
  59.         Case INIT_OBJECT
  60.             WaterPart = Self(hObj, SET_DIAMETER, value)
  61.         Case DISPLAY_OBJECT
  62.             ' code to display the part
  63.         Case READ_OBJECT
  64.             ' code to read info about the part from a file
  65.         Case WRITE_OBJECT
  66.             ' code to write part info to a file
  67.         Case PRINT_OBJECT
  68.             ' code to print the part to a printer
  69.         Case INIT_CLASS
  70.             ReDim WaterPartInstances(1 To MAX_WATERPARTS) As waterPartObject
  71.         Case Else
  72.             a = "Object Doesn't Understand Message." + Chr$(10) + Chr$(10)
  73.             a = a + "Class:         Part" + Chr$(10)
  74.             a = a + "Object:       " + Str$(hObj) + Chr$(10)
  75.             a = a + "Message:  " + Str$(msg) + Chr$(10)
  76.             a = MsgBox(a, 16, "Message Error")
  77.     End Select 
  78. End Function
  79.  
  80. Because VB won't let us define user Types within a Function, the Declarations section of our program describes the Type variable that will contain the object's information.
  81.     Type waterPartObject
  82.        hObj as Integer
  83.         diameter As Single
  84.         area As Single
  85.         quantity As Integer
  86.     End Type
  87.  
  88. Also, constants used by our VB program must be declared in the Declarations section.
  89. ' define messages sent to objects
  90.     Global Const NEW_OBJECT = 1
  91.     Global Const INIT_OBJECT = 2
  92.     Global Const DISPLAY_OBJECT = 3
  93.     Global Const READ_OBJECT = 4
  94.     Global Const WRITE_OBJECT = 5
  95.     Global Const INIT_CLASS = 6
  96.     Global Const GET_QUANTITY = 100
  97.     Global Const SET_QUANTITY = 101
  98.     Global Const GET_DIAMETER = 102
  99.     Global Const SET_DIAMETER = 103
  100.     Global Const GET_AREA = 104
  101.  
  102. ' define max number of water parts
  103.     Global Const MAX_WATERPARTS = 30
  104.  
  105. A language is considered object oriented if it supports three major features:
  106.        1.  Encapsulation (or information and implementation hiding)
  107.        2.  Inheritance.
  108.        3.  Polymorphism
  109. The Function helps us with Encapsulation.  Both the data and the methods that work with the data are contained solely within the Function.  The Type we defined contains all of the instance variables of an object.  An array is created to hold the instance variables of each object of the class.  There are many different ways to find the instance data within the array for a specific object.  In this example, we simply do a brute search for the key, hObj within the array.
  110.  
  111. The data is not globally defined, so the only way to access an object's data is by the methods that are defined within the Function.  We work with an object by passing the handle of the object (hObj) to the Function along with the message that we want and any additional we need to provide.  To get the area of the particular part, anObj,  we would send the following:
  112.        area = WaterPart(anObj, GET_AREA, Null)
  113.  
  114. Similarly, to set a new diameter, we would send the following message:
  115.        returnValue = WaterPart(anObj, SET_DIAMETER, 4.55)
  116.  
  117. One difference between this technique with VB and a real OOP is that the number of parameters sent to a Function is constant.  In a real OOP, each method would be defined separately, with the specific number of parameters it needed. Because we're wrapping our class and all of its methods within one Function, the number of parameters is fixed.  Therefore, each message consist of three parameters, even if you don't need all three.  Simply send a Null for any parameter that isn't needed.  Likewise, there might be some cases that need more than the three parameters specified.  With QB, I had defined the third parameter as a string and encoded all information I needed into that string. The Function then parsed out the info as it needed it. VB is a lot more flexible in this regard.  I haven't tried it yet, but by defining the third parameter as being of the Variant type, I believe you can send any type of variable you wish as the third parameter.  Perhaps even a user defined Type or array could be sent as the third parameter.
  118. Likewise,  QB required that you defined the variable type that the Function returned.  As a result, I defined all the class functions as being strings. That way, I could encode any variables being returned from the class function into the string.  A VB Function again is more flexible, as it doesn't require you to define the variable type it is, and you can return any type of variable you want back from the function.
  119. All of this allows us the advantages of Encapsulation and information and implementation hiding that OOP's normally provide with VB.
  120. The second major feature of an OOP is inheritance.  We can easily model that within VB.  Let's define another class, Pipe, that is a descendant of the WaterPart class we've already defined.
  121. Function Pipe(hObj, msg, value)
  122.  
  123.     Static pipeInstances() As pipeObject
  124.     Static pipeCount%
  125.  
  126. ' find instance data for this object
  127.     For idx = 1 To hoseCount%
  128.         If pipeInstances(idx).hObj = hObj Then
  129.             Exit For
  130.         End If
  131.     Next idx
  132.  
  133. ' methods
  134.     Select Case msg
  135.         Case GET_LENGTH
  136.            Pipe = pipeInstances(idx).length
  137.         Case SET_LENGTH
  138.             If value <> pipeInstances(idx).length Then
  139.                 pipeInstances(idx).length = value
  140.                 pipeInstances(idx).volume = Pipe(hObj, GET_DIAMETER, Null)
  141.             End If
  142.             Pipe = pipeInstances(idx).length
  143.         Case GET_VOLUME
  144.             Pipe = pipeInstances(idx).volume
  145.         Case SET_DIAMETER
  146.             diameter = WaterPart(hObj, msg, value)
  147.             pipeInstances(idx).volume = diameter * pipeInstances(idx).length
  148.         Case Else
  149.             ' message not handled by this class, pass on to ancestor
  150.                 Pipe = WaterPart(hObj, msg, value)
  151.     End Select
  152. End Function
  153.  
  154. This has been simplified quite a lot, but basically, the only difference Pipe objects have from the parent class, WaterPart is that Pipes have a length in addition to a diameter.  Therefore this class has to process all info in regards to the length of the Pipe.  Note the Case Else statement though.  If the message hasn't been handled by this class, it is passed on to it's ancestor, which is WaterPart in this case.  Note that in the WaterPart class function, the Case Else statement has an error trap as the WaterPart class doesn't have an ancestor class, it is a top level class.
  155. Notice that the Pipe class has a SET_DIAMETER method.  This overrides the ancestor WaterPart classes method.  Actually, it does call it first and then recalculates the volume within the pipe.  This brings up a very significant point, if the class sends a message to itself, it needs to be concerned that any overridden methods in its descendants get a chance to process the message first.  This is handled within my OOP techniques by calling a special function, Self.  Notice in the WaterPart class, that both NEW_OBJECT and INIT_OBJECT send messages to Self.  Here's the code for the Self object.
  156. ' route a message to the appropriate class of the object
  157.  
  158. Function Self (hObj, msg, value)
  159.  
  160.     ' find the class of the object by looking it up in the object
  161.     ' class array
  162.         Select Case objectClass%(hObj)
  163.             Case WATERPART_CLASS
  164.                 Self = WaterPart(hObj, msg, value)
  165.             Case PIPE_CLASS
  166.                 Self = Pipe(hObj, msg, value)
  167.         End Select
  168.  
  169. End Function
  170.  
  171. objectClass%() is an array that contains an entry for each object in the program designating what class the object is.  Self() simply redirects the message to the appropriate class for the object specified by hObj.  Here are the class designators as listed in the Declarations section of the program.
  172. ' class identifiers
  173.     Global Const WATERPART_CLASS = 1
  174.     Global Const PIPE_CLASS = 2
  175.  
  176. This gives us most of the late binding features of OOP's.  Its not as elegant or automatic as it is in most OOP's, but it does work.  Just make sure when you create an object, you list its class in the objectClass%() array.  A similar array could also be created to list the ancestor class of the object.
  177. The last major characteristic to be modeled is polymorphism.  This means that there might be several messages that are system wide and that all (or most) classes can respond to.  In our example, messages such as NEW_OBJECT, INIT_OBJECT, DISPLAY_OBJECT, READ_OBJECT, WRITE_OBJECT, PRINT_OBJECT, and INIT_CLASS are examples of polymorphic messages that most classes implement. The sender of the message doesn't need to know about the class, it simply sends the message. The class handles the details.  The Self() function is again used to when sending a message to an object in these cases, as the sender doesn't know even the class of the object that it is sending the message to.
  178. Well, that's most of the techniques I've developed for making QB and VB more like a pure OOP.  I'm not proposing these techniques as being the best or only way to program!  They are simply the techniques that I have developed to provide most of the features I had with Actor in QB and VB.  As I work with them, I am refining them and making them more efficient.  But the overhead and code to make it work is more complex than it would be if this was a pure OOP.
  179.